package worker

import (
	"context"
	"fmt"
	"net"
	"study-crontab/crontab/common"
	"time"

	clientv3 "go.etcd.io/etcd/client/v3"
)

//注册节点到etcd
type Register struct {
	client *clientv3.Client
	kv     clientv3.KV
	lease  clientv3.Lease

	locakIp string //本机ip
}

var G_register *Register

func getLocalIP() (ipv4 string, err error) {

	//获取所有网卡
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return
	}
	//取第一个非lo的网卡IP
	for _, addr := range addrs {
		//网络地址反解为ip地址
		if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
			//跳过ipv6
			if ipNet.IP.To4() != nil {
				ipv4 = ipNet.IP.String()
				return
			}
		}
	}
	err = common.ERR_NO_LOCAL_IP_FOUND
	return
}

// NewRegister 初始化管理器
func NewRegister() error {
	config := clientv3.Config{
		Endpoints:   G_config.EtcdEndpoints,
		DialTimeout: time.Duration(G_config.EtcdDialTimeout) * time.Millisecond,
	}

	//建立连接
	client, err := clientv3.New(config)
	if err != nil {
		fmt.Println("etcd连接失败")
		return err
	}

	localIP, err := getLocalIP()
	if err != nil {
		return err
	}

	kv := clientv3.NewKV(client)
	lease := clientv3.NewLease(client)

	G_register = &Register{
		client:  client,
		kv:      kv,
		lease:   lease,
		locakIp: localIP,
	}

	// 服务注册
	go G_register.keepOnline()
	return nil
}

//注册到etcd中
func (r *Register) keepOnline() {
	lease := r.lease
	var (
		regKey         string
		leaseGrantResp *clientv3.LeaseGrantResponse
		err            error
		keepAliveChan  <-chan *clientv3.LeaseKeepAliveResponse
		keepAliveResp  *clientv3.LeaseKeepAliveResponse
		leaseId        clientv3.LeaseID
		cancelCtx      context.Context
		cancelFunc     context.CancelFunc
	)

	for {
		// 注册路径
		regKey = common.REGISTER_WORKERS_DIR + r.locakIp

		cancelFunc = nil

		// 创建租约
		if leaseGrantResp, err = lease.Grant(context.TODO(), 10); err != nil {
			goto RETRY
		}

		//拿到租约的ID
		leaseId = leaseGrantResp.ID
		// 自动续租
		if keepAliveChan, err = lease.KeepAlive(context.TODO(), leaseId); err != nil {
			goto RETRY
		}

		cancelCtx, cancelFunc = context.WithCancel(context.TODO())

		// 注册到etcd
		if _, err = r.kv.Put(cancelCtx, regKey, "", clientv3.WithLease(leaseGrantResp.ID)); err != nil {
			goto RETRY
		}

		// 处理续租应答
		for {
			select {
			case keepAliveResp = <-keepAliveChan:
				if keepAliveResp == nil { // 续租失败
					goto RETRY
				}
			}
		}

	RETRY:
		time.Sleep(1 * time.Second)
		if cancelFunc != nil {
			cancelFunc()
			lease.Revoke(context.TODO(), leaseId)
		}
	}
}
